<!doctype html>
<title>CSS 3D Transforms tests</title>
<link rel=author title="Aryeh Gregor" href="mailto:ayg@aryeh.name">
<script src=http://w3c-test.org/resources/testharness.js></script>
<script src=http://w3c-test.org/resources/testharnessreport.js></script>
<style>
/* See 2d-transforms.html for info on styles, which are copy-pasted for now */
body {
	margin: -15px;
	width: 130px;
	height: 80px;
}
body > div {
	width: 120px;
	height: 70px;
	padding: 5px;
	background: orange;
}
body > div > div {
	width: 110px;
	height: 60px;
	padding: 5px;
	background: yellow;
}
body, div {
	position: relative;
}
#test {
	position: static;
	height: 30px;
	width: 80px;
	padding: 5px;
	border: 5px solid black;
	margin: 5px;
	background: blue;
}
#log { display: none }
</style>
<style class=switch></style>
<style class=switch>
div { float: left }
</style>
<style class=switch>
div { float: right }
</style>
<style class=switch>
div { float: right }
body { width: 180px; margin-left: -65px }
</style>
<style class=switch>
#test { position: absolute }
</style>
<style class=switch>
body > div > div { border-right: 10px solid transparent; width: 100px }
#test { position: absolute; right: -5px }
</style>
<style class=switch>
body > div > div { padding-left: 10px; width: 105px }
#test { position: relative; left: -5px }
</style>
<style class=switch>
#test { display: inline-block }
</style>
<style class=switch>
#test { display: table }
</style>
<div><div><div id=test></div></div></div>
<div id=log></div>
<script src="transforms.js"></script>
<script>
"use strict";

section = "parsing";

// Test case-sensitivity and other parsing issues
[
	// Parse errors; we prepend scale(2) to ensure that any resulting identity
	// matrix is really due to a parse error and not just the function
	// happening to evaluate to the identity matrix
	[[
	 // No arguments
	 "scale(2) matrix3d()", "scale(2) translate3d()", "scale(2) translateZ()",
	 "scale(2) scale3d()", "scale(2) scaleZ()", "scale(2) rotate3d()",
	 "scale(2) rotateX()", "scale(2) rotateY()", "scale(2) rotateZ()",
	 "scale(2) perspective()",
	 // Too few arguments
	 "scale(2) matrix3d(1,2,3,4,5,6)",
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12)",
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)",
	 "scale(2) translate3d(5px, 5px)", "scale(2) scale3d(2, 2)",
	 "scale(2) rotate3d(90deg)", "scale(2) rotate3d(1)",
	 "scale(2) rotate3d(1,0)", "scale(2) rotate3d(1,0,0)",
	 // Too many arguments
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17)",
	 "scale(2) translate3d(5px,5px,5px,5px)", "scale(2) translateZ(5px,5px)",
	 "scale(2) scale3d(2,2,2,2)", "scale(2) scaleZ(2,2)",
	 "scale(2) rotate3d(1,0,0,0,90deg)",
	 "scale(2) rotateX(90deg,90deg)", "scale(2) rotateX(1,0,0,90deg)",
	 "scale(2) rotateY(90deg,90deg)", "scale(2) rotateY(1,0,0,90deg)",
	 "scale(2) rotateZ(90deg,90deg)", "scale(2) rotateZ(1,0,0,90deg)",
	 // Wrong unit
	 "scale(2) translate3d(10,10,10)", "scale(2) translate3d(10px,10px,10)",
	 "scale(2) translate3d(10px,10,10px)",
	 "scale(2) translate3d(10,10px,10px)",
	 "scale(2) translate3d(10px,10px,10%)",
	 "scale(2) translateZ(10)", "scale(2) translateZ(10%)",
	 "scale(2) scale3d(10,10,10px)", "scale(2) scale3d(10,10px,10)",
	 "scale(2) scale3d(10px,10,10)", "scale(2) scale3d(10px,10px,10px)",
	 "scale(2) scale3d(10,10,10%)", "scale(2) scale3d(10,10%,10)",
	 "scale(2) scale3d(10%,10,10)", "scale(2) scale3d(10%,10%,10%)",
	 "scale(2) scaleZ(10px)", "scale(2) scaleZ(10%)",
	 "scale(2) rotate3d(0,0,1,10px)", "scale(2) rotate3d(1px,0,0,90deg)",
	 "scale(2) rotate3d(1,0px,0,90deg)", "scale(2) rotate3d(1,0,0px,90deg)",
	 "scale(2) rotate3d(90deg,1,0,0)", "scale(2) rotate3d(1,0,0,1)",
	 "scale(2) rotate3d(10%,10%,10%,90deg)", "scale(2) rotate3d(1,0,0,10%)",
	 "scale(2) rotateX(10px)", "scale(2) rotateX(10%)", "scale(2) rotateX(10)",
	 "scale(2) rotateY(10px)", "scale(2) rotateY(10%)", "scale(2) rotateY(10)",
	 "scale(2) rotateZ(10px)", "scale(2) rotateZ(10%)", "scale(2) rotateZ(10)",
	 "scale(2) perspective(100)", "scale(2) perspective(10deg)",
	 "scale(2) perspective(10%)",
	 // perspective() with nonpositive lengths is not allowed
	 "scale(2) perspective(-1px)", "scale(2) perspective(-100em)",
	 "scale(2) perspective(0pt)", "scale(2) perspective(0)",
	 // The spec says <number> for all entries, so lengths and percentages are
	 // not allowed in any of the entries.
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,1px,14,15,16)",
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,13,1px,15,16)",
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,13,14,1px,16)",
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,1px,1px,1px,16)",
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,1%,14,15,16)",
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,13,1%,15,16)",
	 // Even if we allowed <translation-value>s, we would only allow <length>
	 // for the z-translation entry, so these should be parse errors regardless
	 // (and indeed they are, even in Gecko).
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,13,14,1%,16)",
	 "scale(2) matrix3d(1,2,3,4,5,6,7,8,9,10,11,12,1%,1%,1%,16)",
	], ""],
	[["matrix3d(1,2,3,0,4,5,6,0,7,8,9,0,10,11,12,1)",
	  "MATRIX3D(1,2,3,0,4,5,6,0,7,8,9,0,10,11,12,1)",
	  " mAtRiX3d( 1,2,3,0,4 ,5,6,0,7,8 , 9,0,10, 11,12,1 )  "],
	 "matrix3d(1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 10, 11, 12, 1)",
	 1, 2, 3, 0,  4, 5, 6, 0,  7, 8, 9, 0,  10, 11, 12, 1],
	[["translate3d(21px, -6px, 4px)",
	  "TRANSLATE3D(21PX, -6PX, 4PX)",
	  " tRaNsLaTe3D( 21pX,-6pX , 4pX )  "],
	 "translate3d(21px, -6px, 4px)",
	 1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  21, -6, 4, 1],
	[["translatez(15.4px)","TRANSLATEZ(15.4PX)",  " tRaNsLaTeZ( 15.4pX ) "],
	 "translateZ(15.4px)",
	 1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  0, 0, 15.4, 1],
	[["scale3d(2, 4, -9)","SCALE3D(2, 4, -9)",  " sCaLe3D( 2,4 ,  -9 )  "],
	 "scale3d(2, 4, -9)",
	 2, 0, 0, 0,  0, 4, 0, 0,  0, 0, -9, 0,  0, 0, 0, 1],
	[["scalez(2)","SCALEZ(2)",  " sCaLeZ( 2 ) "],
	 "scaleZ(2)",
	 1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 2, 0,  0, 0, 0, 1],
	[["rotate3d(0, 0, 1, 180deg)",
	  "ROTATE3D(0, 0, 1, 180DEG)",
	  " rOtAtE3d( 0,0 , 1 ,180DeG ) "],
	 "rotate3d(0, 0, 1, 180deg)",
	 -1, 0, 0, 0,  0, -1, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1],
	// Rotating around the zero vector should be a no-op
	[["scale(2) rotate3d(0, 0.0, 0, 180deg)"],
	 "scale(2) rotate3d(0, 0, 0, 180deg)", 2, 0, 0, 2, 0, 0],
	[["rotatex(180deg)",
	  "ROTATEX(180DEG)",
	  " rOtAtEx( 180DeG ) "],
	 "rotateX(180deg)",
	 1, 0, 0, 0,  0, -1, 0, 0,  0, 0, -1, 0,  0, 0, 0, 1],
	[["rotatey(180deg)",
	  "ROTATEY(180DEG)",
	  " rOtAtEy( 180DeG ) "],
	 "rotateY(180deg)",
	 -1, 0, 0, 0,  0, 1, 0, 0,  0, 0, -1, 0,  0, 0, 0, 1],
	[["rotatez(180deg)",
	  "ROTATEZ(180DEG)",
	  " rOtAtEz( 180DeG ) "],
	 "rotateZ(180deg)",
	 -1, 0, 0, 0,  0, -1, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1],
	[["perspective(400px)",
	  "PERSPECTIVE(400PX)",
	  " pErSpEcTiVe( 400Px ) "],
	 "perspective(400px)",
	 1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, -1/400,  0, 0, 0, 1],
	// Serialization of unitless zeroes
	[["translate3d(0,0,0)"], "translate3d(0px, 0px, 0px)", 1, 0, 0, 1, 0, 0],
	[["translatez(0)"], "translateZ(0px)", 1, 0, 0, 1, 0, 0],
	[["rotate3d(1,0,0,0)"], "rotate3d(1, 0, 0, 0deg)", 1, 0, 0, 1, 0, 0],
	[["rotatex(0)"], "rotateX(0deg)", 1, 0, 0, 1, 0, 0],
	[["rotatey(0)"], "rotateY(0deg)", 1, 0, 0, 1, 0, 0],
	[["rotatez(0)"], "rotateZ(0deg)", 1, 0, 0, 1, 0, 0],
	// Test a whole bunch of functions mashed together
	[["tranSlatex( 16px )rotatez(-90deg)  rotate(100grad)\ttranslate3d(-12pt, 0, 0.0em)"],
	 "translateX(16px) rotateZ(-90deg) rotate(100grad) translate3d(-12pt, 0px, 0em)",
	 1, 0, 0, 1, 0, 0],
].forEach(function(arr) {
	arr[0].forEach(function(transform, i) {
		div.setAttribute("style", "");
		div.removeAttribute("style");
		div.style[prefixProp("transform")] = transform;
		test(function() {
			testInlineTransform(arr[1]);
		}, 'div.style.cssText and .transform ' + 'with "transform: ' + transform
		+ '" set via CSSOM');
		test(function() {
			testComputedTransform(arr.slice(2));
		}, 'getComputedStyle(div).transform with "transform: ' + transform
		+ '" set via CSSOM');
	});
});
div.removeAttribute("style");

// Test style="transform: matrix3d(*)" for various inputs.  I've pretty-printed
// everything as 4x4 matrices, but they're actually transposed.
section = "matrix";
(function(){
var testMatrices = [
	// 2D matrices in disguise
	[-1.72, -3.57, 0, 0,
	 -3.95,  2.29, 0, 0,
	 -3.74,  4.89, 1, 0,
	 -2.00,  0.95, 0, 1],
	[27.51,  19.40, 0, 0,
	-37.24, -18.45, 0, 0,
	 24.12,  -3.30, 1, 0,
	 32.76, -36.50, 0, 1],
	// Bottom row [0, 0, 0, 1]
	[-1.72, -3.57,  0.15, 0,
	 -3.95,  2.29, -1.53, 0,
	 -3.74,  4.89,  0.34, 0,
	 -2.00,  0.95,  0.53, 1],
	[27.51,  19.40, -45.68, 0,
	-37.24, -18.45,  41.11, 0,
	 24.12,  -3.30, -46.37, 0,
	 32.76, -36.50,  18.25, 1],
	// All entries random
	[-1.72, -3.57,  0.15, -0.79,
	 -3.95,  2.29, -1.53,  3.77,
	 -3.74,  4.89,  0.34,  4.30,
	 -2.00,  0.95,  0.53, -3.97],
	[27.51,  19.40, -45.68,  -0.17,
	-37.24, -18.45,  41.11, -14.74,
	 24.12,  -3.30, -46.37,   4.60,
	 32.76, -36.50,  18.25,  -5.34],
];
// Add the identity matrix with one or two arbitrary entries changed to 10.
// This is a fairly cheap form of fuzz-testing that can catch bugs like
// https://bugzilla.mozilla.org/show_bug.cgi?id=718809
for (var i = 0; i < 16; i++) {
	for (var j = 0; j <= i; j++) {
		var mx = [1, 0, 0, 0,  0, 1, 0, 0,  0, 0, 1, 0,  0, 0, 0, 1];
		mx[i] = mx[j] = 10;
		testMatrices.push(mx);
	}
}
testMatrices.forEach(function(mx) {
	testTransform("matrix3d(" + mx.join(", ") + ")", mx);
	testTransformedBoundary("matrix3d(" + mx.join(", ") + ")", mx, "0 0", 0, 0);
});
})();

// Test translate3d()/translateZ()
section = "translate";
lengths
.slice(0, 10)
.forEach(function(tz) {
	testTransform(
		"translateZ(" + tz + ")",
		[1, 0, 0, 0,
		 0, 1, 0, 0,
		 0, 0, 1, 0,
		 0, 0, convertToPx(tz), 1]
	);

	percentagesAndLengths
	.slice(0, 5)
	.forEach(function(ty) {
		percentagesAndLengths
		.slice(0, 5)
		.forEach(function(tx) {
			testTransform(
				"translate3d(" + tx + ", " + ty + ", " + tz + ")",
				[1, 0, 0, 0,
				 0, 1, 0, 0,
				 0, 0, 1, 0,
				 convertToPx(tx, divWidth),
				 convertToPx(ty, divHeight),
				 convertToPx(tz), 1]
			);
		});
	});
});

// Test scale3d()/scaleZ()
section = "scale";
(function(){
var scales = [-2, -0.12, 0, 0.12, 2];
scales.forEach(function(sz) {
	testTransform(
		"scaleZ(" + sz + ")",
		[1, 0, 0, 0,
		 0, 1, 0, 0,
		 0, 0, sz, 0,
		 0, 0, 0, 1]
	);

	scales.forEach(function(sy) {
		scales.forEach(function(sx) {
			testTransform(
				"scale3d(" + sx + ", " + sy + ", " + sz + ")",
				[sx, 0, 0, 0,
				 0, sy, 0, 0,
				 0, 0, sz, 0,
				 0, 0, 0, 1]
			);
		});
	});
});
})();

// Test rotate3d(), rotateX(), rotateY(), rotateZ()
section = "rotate";
rotateAngles.forEach(function(angle) {
	var rads = convertToRad(angle);
	testTransform(
		"rotateX(" + angle + ")",
		getRotationMatrix(1, 0, 0, angle)
	);
	testTransform(
		"rotateY(" + angle + ")",
		getRotationMatrix(0, 1, 0, angle)
	);
	testTransform(
		"rotateZ(" + angle + ")",
		getRotationMatrix(0, 0, 1, angle)
	);

	var vectors = [
		[1, 0, 0],
		[0, 1, 0],
		[0, 0, 1],
		[1, -1, 0],
		[3, -17.2, 0.14],
	];
	vectors.forEach(function(vec) {
		testTransform(
			"rotate3d(" + vec.join(", ") + ", " + angle + ")",
			getRotationMatrix(vec[0], vec[1], vec[2], angle)
		);
	});
});

// Test perspective()
section = "perspective";
lengths
.concat("2000px", "1000px", "200px", "0.001px")
.forEach(function(length) {
	var pixels = convertToPx(length);
	if (pixels <= 0) {
		return;
	}
	testTransform(
		"perspective(" + length + ")",
		[1, 0, 0, 0,
		 0, 1, 0, 0,
		 0, 0, 1, -1/pixels,
		 0, 0, 0, 1]
	);
});

// Test multiple transformations
section = "multiple";
(function(){
var transforms = [
	["matrix3d(0.98, 1.06, 4.07, 3.29, "
	 + "4.06, -1.82, -4.09, -3.69, "
	 + "0.22, 4.42, 1.29, -1.76, "
	 + "-3.59, 4.05, 3.36, 2.47)",
	 0.98, 1.06, 4.07, 3.29,
	 4.06, -1.82, -4.09, -3.69,
	 0.22, 4.42, 1.29, -1.76,
	 -3.59, 4.05, 3.36, 2.47],
	["translate3d(0.23in, 17pt, -0.6em)",
	 1, 0, 0, 0,
	 0, 1, 0, 0,
	 0, 0, 1, 0,
	 convertToPx("0.23in"), convertToPx("17pt"), convertToPx("-0.6em"), 1],
	["scale3d(0.2, -1, 2)",
	 0.2, 0, 0, 0,
	 0, -1, 0, 0,
	 0, 0, 2, 0,
	 0, 0, 0, 1],
	["rotate3d(0, 0.6, 0.8, 90deg)"]
	 .concat(getRotationMatrix(0, 0.6, 0.8, "90deg")),
	["perspective(200px)",
	 1, 0, 0, 0,
	 0, 1, 0, 0,
	 0, 0, 1, -0.005,
	 0, 0, 0, 1],
];
transforms.forEach(function(trans1) {
	testTransform(trans1[0], trans1.slice(1));

	transforms.forEach(function(trans2) {
		var mx = mxmul44(trans1.slice(1), trans2.slice(1));

		// First put both transforms on the test div
		testTransform(trans1[0] + " " + trans2[0], mx);

		// Now put the first on its grandparent, and the second on its parent.
		// No need to test parsing.
		setStyles({},
			{transform: trans2[0], transformStyle: "preserve-3d"},
			{transform: trans1[0], transformStyle: "preserve-3d"});
		testTransformedBoundary("none", mx);
		setStyles({}, {}, {});

		transforms.forEach(function(trans3) {
			var mx = mxmul44(trans1.slice(1), trans2.slice(1), trans3.slice(1));

			testTransform(trans1[0] + " " + trans2[0] + " " + trans3[0], mx);

			setStyles({},
				{transform: trans2[0], transformStyle: "preserve-3d"},
				{transform: trans1[0], transformStyle: "preserve-3d"});
			testTransformedBoundary(trans3[0], mx);

			// Now try with transform-style: flat.  This is the same as
			// premultiplying by scaleZ(0), as far as rendering goes.
			var flattenMx = [1,0,0,0, 0,1,0,0, 0,0,0,0, 0,0,0,1];
			setStyles({},
				{transform: trans2[0]},
				{transform: trans1[0], transformStyle: "preserve-3d"});
			testTransformedBoundary(trans3[0],
				mxmul44(trans1.slice(1), trans2.slice(1), flattenMx,
				trans3.slice(1))
			);

			setStyles({},
				{transform: trans2[0]},
				{transform: trans1[0]});
			testTransformedBoundary(trans3[0],
				mxmul44(trans1.slice(1), flattenMx, trans2.slice(1),
				flattenMx, trans3.slice(1))
			);
			setStyles({}, {}, {});
		});

	});
});
})();


// Test transform-origin
section = "transform-origin";
percentagesAndLengths
.slice(0, 7)
.forEach(function(arg3) {
	// Ordered variant
	["left", "center", "right"]
	.concat(percentagesAndLengths.slice(0, 5))
	.forEach(function(arg1) {
		["top", "center", "bottom"]
		.concat(percentagesAndLengths.slice(0, 5))
		.forEach(function(arg2) {
			if (/%$/.test(arg3)) {
				// Parse error: no percentages allowed in the Z component
				testTransformOrigin(arg1 + " " + arg2 + " " + arg3, "50%", "50%", 0);
			} else {
				testTransformOrigin(arg1 + " " + arg2 + " " + arg3, arg1, arg2, arg3);
			}
		});
	});

	// Reverse order is allowed if only keywords are used in the first two
	// arguments
	["top", "center", "bottom"].forEach(function(arg1) {
		["left", "center", "right"].forEach(function(arg2) {
			if (arg1 == "center" && arg2 == "center") {
				// Already tested
				return;
			}
			if (/%$/.test(arg3)) {
				// Parse error: no percentages allowed in the Z component
				testTransformOrigin(arg1 + " " + arg2 + " " + arg3, "50%", "50%", 0);
			} else {
				testTransformOrigin(arg1 + " " + arg2 + " " + arg3, arg2, arg1, arg3);
			}
		});
	});
});

// Four values is a parse error
testTransformOrigin("left 10px bottom -10px", "50%", "50%", 0);


section = "perspective-origin";

setStyles({}, {}, {});

// Test perspective-origin with zero and one argument.
[
	["", "50%", "50%"],
	["none", "50%", "50%"],
	["NONE", "50%", "50%"],
	["nOnE", "50%", "50%"],
	["quasit", "50%", "50%"],
	["top", "50%", "0%"],
	["TOP", "50%", "0%"],
	["tOp", "50%", "0%"],
	["right", "100%", "50%"],
	["RIGHT", "100%", "50%"],
	["rIgHt", "100%", "50%"],
	["bottom", "50%", "100%"],
	["BOTTOM", "50%", "100%"],
	["bOtToM", "50%", "100%"],
	["left", "0%", "50%"],
	["LEFT", "0%", "50%"],
	["lEfT", "0%", "50%"],
	["center", "50%", "50%"],
	["CENTER", "50%", "50%"],
	["cEnTeR", "50%", "50%"],
	["37%", "37%", "50%"],
	["117%", "117%", "50%"],
	["41.2px", "41.2px", "50%"],
	["-31.8px", "-31.8px", "50%"],
].forEach(function(arr) {
	testPerspectiveOrigin(arr[0], arr[1], arr[2]);
});

// Test perspective-origin with two arguments.
["left", "center", "right"].concat(percentagesAndLengths).forEach(function(arg1) {
	["top", "center", "bottom"].concat(percentagesAndLengths).forEach(function(arg2) {
		testPerspectiveOrigin(arg1 + " " + arg2, arg1, arg2);
	});
});

// Test in backwards order too
["top", "center", "bottom"].forEach(function(arg1) {
	["left", "center", "right"].forEach(function(arg2) {
		if (arg1 == "center" && arg2 == "center") {
			// Already tested
			return;
		}
		testPerspectiveOrigin(arg1 + " " + arg2, arg2, arg1);
	});
});

// Test inheritance.  perspective-origin should be inherited as an absolute
// length, not relative; but percentages should be resolved after inheritance.
setStyles({}, {perspectiveOrigin: "25px 25px"}, {});
testPerspectiveOrigin("inherit", "25px", "25px");
setStyles({}, {perspectiveOrigin: "0% 75%"}, {});
testPerspectiveOrigin("inherit", "0%", "75%");
setStyles({},
	{perspectiveOrigin: "inherit", fontSize: "0.5em"},
	{perspectiveOrigin: "1em 1em", fontSize: "2em"});
testPerspectiveOrigin("inherit", "2em", "2em");
setStyles({}, {}, {});

// Three or more arguments are invalid.
testPerspectiveOrigin("10px 20px 30px", "50%", "50%");
testPerspectiveOrigin("10px 20px 30px 40px", "50%", "50%");
testPerspectiveOrigin("10px 20px 30px 40px 50px", "50%", "50%");
testPerspectiveOrigin("left 25px center", "50%", "50%");
testPerspectiveOrigin("left 25px center -10px", "50%", "50%");

section = "perspective-prop";

testPerspective("", "", "50%", "50%");
testPerspective("none", "", "50%", "50%");
testPerspective("NONE", "", "50%", "50%");
testPerspective("nOnE", "", "50%", "50%");
lengths
.concat("2000px", "1000px", "200px", "0.001px")
.forEach(function(length) {
	[
		["", "50%", "50%"],
		["8em -22px", "8em", "-22px"],
		["0 0", "0", "0"],
		["1000px 1000px", "1000px", "1000px"],
		["top left", "top", "left"],
	].forEach(function(origin) {
		testPerspective(length, origin[0], origin[1], origin[2]);
	});
});

// Test inheritance.  perspective should be inherited as an absolute length,
// not relative.
//
// Note that only the perspective of the parent counts!  Grandparents don't do
// anything with transform-style: flat (the default).
setStyles({}, {}, {perspective: "100px"});
testPerspectiveParsing("none");
testPerspectiveBoundary("none");

setStyles({}, {perspective: "inherit"}, {perspective: "100px"});
testPerspectiveParsing("100px");
testPerspectiveBoundary("100px");

setStyles({}, {perspective: "20em"}, {});
testPerspectiveParsing("20em");
testPerspectiveBoundary("20em");

setStyles({}, {perspective: "inherit", fontSize: "0.5em"},
	{perspective: "10em", fontSize: "2em"});
testPerspectiveParsing("20em");
testPerspectiveBoundary("20em");

setStyles({}, {perspective: "inherit"},
	{perspective: "inherit", fontSize: "0.5em"},
	{perspective: "10em", fontSize: "2em"});
testPerspectiveParsing("20em");
testPerspectiveBoundary("20em");

setStyles({}, {}, {}, {});

[].forEach.call(document.querySelectorAll("style"), function(style) {style.disabled = true});
div.parentNode.removeChild(div);
</script>
